#![allow(dead_code)]
use std::fmt::Display;
use std::string::FromUtf8Error;

use crate::chunk_type::ChunkType;
use crate::result::Error;
use crate::result::Result;

#[derive(Debug)]
pub struct Chunk {
    //长度，4个字节=4*8
    length: u32,
    //也是4个字节
    chunk_type: ChunkType,
    data: Vec<u8>,
    //4个字节长度，用来检测是否有错误的循环冗余码
    crc: u32,
}
impl Chunk {
    pub const CHUNK_TYPE_BYTES_LEN: usize = 4;
    pub const CRC_BYTES_LEN: usize = 4;
    pub const DATA_LENS_BYTES_LEN: usize = 4;

    pub fn new(chunk_type: ChunkType, data: Vec<u8>) -> Self {
        let crc = crc_computed(chunk_type, data.as_slice());
        Self {
            chunk_type,
            length: data.len() as u32,
            data,
            crc,
        }
    }
    pub fn chunk_type(&self) -> ChunkType {
        self.chunk_type
    }
    pub fn length(&self) -> u32 {
        self.length
    }
    pub fn crc(&self) -> u32 {
        self.crc
    }
    pub fn data(&self) -> &[u8] {
        &self.data
    }
    pub fn data_as_string(&self) -> std::result::Result<String, FromUtf8Error> {
        String::from_utf8(self.data.clone())
    }

    pub fn as_bytes(&self) -> Vec<u8> {
        self.length
            .to_be_bytes()
            .iter()
            .chain(self.chunk_type.bytes().iter())
            .chain(self.data.iter())
            .chain(self.crc.to_be_bytes().iter())
            .copied()
            .collect()
    }
}

impl Display for Chunk {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "length:{length},chunkType:{chunk_type},crc:{crc},data_as_string:{data}",
            length = self.length,
            chunk_type = self.chunk_type,
            crc = self.crc,
            data = self
                .data_as_string()
                .unwrap_or("can't convert to string".to_string())
        )
    }
}
fn crc_computed(chunk_type: ChunkType, data: &[u8]) -> u32 {
    let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
    let data = chunk_type
        .bytes()
        .iter()
        .chain(data.iter())
        .copied()
        .collect::<Vec<u8>>();

    crc.checksum(data.as_slice())
}
impl TryFrom<&Vec<u8>> for Chunk {
    type Error = Error;

    fn try_from(value: &Vec<u8>) -> Result<Self> {
        let (len, remainder) = value.split_at(4);
        let length = u32::from_be_bytes(len.try_into().unwrap());
        let (c_type, remainder) = remainder.split_at(4);
        let chunk_type = ChunkType::from(TryInto::<[u8; 4]>::try_into(c_type).unwrap());
        let remainder_len = remainder.len();
        let (data, crc) = remainder.split_at(remainder_len - 4);
        let crc_32 = u32::from_be_bytes(crc.try_into().unwrap());
        let crc_computed = crc_computed(chunk_type, data);
        if crc_computed == crc_32 && data.len() == length as usize {
            Ok(Self {
                length,
                data: data.to_vec(),
                crc: crc_computed,
                chunk_type,
            })
        } else {
            Err("data len not compairs ".into())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::chunk_type::ChunkType;
    use std::str::FromStr;

    fn testing_chunk() -> Chunk {
        let data_length: u32 = 42;
        let chunk_type = "RuSt".as_bytes();
        let message_bytes = "This is where your secret message will be!".as_bytes();
        let crc: u32 = 2882656334;

        let chunk_data: Vec<u8> = data_length
            .to_be_bytes()
            .iter()
            .chain(chunk_type.iter())
            .chain(message_bytes.iter())
            .chain(crc.to_be_bytes().iter())
            .copied()
            .collect();

        Chunk::try_from(chunk_data.as_ref()).unwrap()
    }

    #[test]
    fn test_new_chunk() {
        let chunk_type = ChunkType::from_str("RuSt").unwrap();
        let data = "This is where your secret message will be!"
            .as_bytes()
            .to_vec();
        let chunk = Chunk::new(chunk_type, data);
        assert_eq!(chunk.length(), 42);
        assert_eq!(chunk.crc(), 2882656334);
    }

    #[test]
    fn test_chunk_length() {
        let chunk = testing_chunk();
        assert_eq!(chunk.length(), 42);
    }

    #[test]
    fn test_chunk_type() {
        let chunk = testing_chunk();
        assert_eq!(chunk.chunk_type().to_string(), String::from("RuSt"));
    }

    #[test]
    fn test_chunk_string() {
        let chunk = testing_chunk();
        let chunk_string = chunk.data_as_string().unwrap();
        let expected_chunk_string = String::from("This is where your secret message will be!");
        assert_eq!(chunk_string, expected_chunk_string);
    }

    #[test]
    fn test_chunk_crc() {
        let chunk = testing_chunk();
        assert_eq!(chunk.crc(), 2882656334);
    }

    #[test]
    fn test_valid_chunk_from_bytes() {
        let data_length: u32 = 42;
        let chunk_type = "RuSt".as_bytes();
        let message_bytes = "This is where your secret message will be!".as_bytes();
        let crc: u32 = 2882656334;

        let chunk_data: Vec<u8> = data_length
            .to_be_bytes()
            .iter()
            .chain(chunk_type.iter())
            .chain(message_bytes.iter())
            .chain(crc.to_be_bytes().iter())
            .copied()
            .collect();

        let chunk = Chunk::try_from(chunk_data.as_ref()).unwrap();

        let chunk_string = chunk.data_as_string().unwrap();
        let expected_chunk_string = String::from("This is where your secret message will be!");

        assert_eq!(chunk.length(), 42);
        assert_eq!(chunk.chunk_type().to_string(), String::from("RuSt"));
        assert_eq!(chunk_string, expected_chunk_string);
        assert_eq!(chunk.crc(), 2882656334);
    }

    #[test]
    fn test_invalid_chunk_from_bytes() {
        let data_length: u32 = 42;
        let chunk_type = "RuSt".as_bytes();
        let message_bytes = "This is where your secret message will be!".as_bytes();
        let crc: u32 = 2882656333;

        let chunk_data: Vec<u8> = data_length
            .to_be_bytes()
            .iter()
            .chain(chunk_type.iter())
            .chain(message_bytes.iter())
            .chain(crc.to_be_bytes().iter())
            .copied()
            .collect();

        let chunk = Chunk::try_from(chunk_data.as_ref());

        assert!(chunk.is_err());
    }

    #[test]
    pub fn test_chunk_trait_impls() {
        let data_length: u32 = 42;
        let chunk_type = "RuSt".as_bytes();
        let message_bytes = "This is where your secret message will be!".as_bytes();
        let crc: u32 = 2882656334;

        let chunk_data: Vec<u8> = data_length
            .to_be_bytes()
            .iter()
            .chain(chunk_type.iter())
            .chain(message_bytes.iter())
            .chain(crc.to_be_bytes().iter())
            .copied()
            .collect();

        let chunk: Chunk = TryFrom::try_from(chunk_data.as_ref()).unwrap();

        let _chunk_string = format!("{}", chunk);
    }
}
